fix: 长会话任务锚点重注入 + 过早 end_turn 提示,缓解任务跑偏#1309
Conversation
长会话反复无锚点催促("继续/接着做")会导致任务锚点衰减、模型过早
end_turn 打转。新增两层客户端护栏(均 token 友好):
- 层3 触发式锚点重注入:仅当最新用户消息是短的无锚点催促且存在未完成
任务时,注入一次 ephemeral <system-reminder> 未完成任务清单(工具循环
内不重复注入,无每轮成本)。
- 层2 过早 end_turn 提示:turn 自然完成且仍有 in_progress 任务时,显示
0-token 本地 system 提示(不发给模型)。
Co-Authored-By: claude-opus-4-8[1m] <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds task reminder utilities and tests, reinjects unfinished-task reminders for short continuation nudges, and appends unfinished-task warnings to the transcript after non-aborted turns. ChangesTask reminder flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant User
participant queryModel
participant TaskUtilities
participant ClaudeAPI
User->>queryModel: send short continuation nudge
queryModel->>TaskUtilities: read unfinished tasks
queryModel->>ClaudeAPI: send nudge with task reminder
ClaudeAPI-->>queryModel: stream query response
queryModel-->>User: continue task processing
sequenceDiagram
participant REPL
participant TaskUtilities
participant Transcript
REPL->>TaskUtilities: read current task list after query completion
TaskUtilities-->>REPL: unfinished tasks
REPL->>Transcript: append warning notice
Transcript-->>User: display unfinished-task warning
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/screens/REPL.tsx`:
- Around line 3533-3536: Update the dynamic imports in the notice injection
within REPL to use the relative paths ../utils/tasks.js and
../services/api/taskAnchorReminder.js instead of src/ aliases, while preserving
the existing imported symbols and Promise.all flow.
In `@src/services/api/taskAnchorReminder.ts`:
- Around line 20-25: Update isAnchorlessNudge to reject trimmed text containing
an explicit task anchor before applying ANCHORLESS_NUDGE_PATTERN, while
preserving existing length and nudge-word validation. Reuse the existing
task-reference detection symbol if available, and add regression coverage
confirming inputs such as “continue Task `#2`” return false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9af9169d-beab-4a26-86e1-3efe3884e075
📒 Files selected for processing (4)
src/screens/REPL.tsxsrc/services/api/__tests__/taskAnchorReminder.test.tssrc/services/api/claude.tssrc/services/api/taskAnchorReminder.ts
| const [{ listTasks, getTaskListId }, { buildUnfinishedTaskNotice }] = await Promise.all([ | ||
| import('src/utils/tasks.js'), | ||
| import('src/services/api/taskAnchorReminder.js'), | ||
| ]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -HI -t f . | rg '/(tsconfig[^/]*\.json|package\.json|bunfig\.toml)$'
rg -n -C2 '"(baseUrl|paths)"|src/' \
--glob 'tsconfig*.json' \
--glob 'package.json' \
--glob 'bunfig.toml' .Repository: claude-code-best/claude-code
Length of output: 18729
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Top-level package manifests/configs:"
fd -HI -t f '/^(package\.json|tsconfig\.json|bunfig\.toml|vite\.config\.[^/]+)$' -d 2 . | sed 's#^\./##' | sort
echo
echo "Top-level package.json scripts and imports:"
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
if p.exists():
data=json.loads(p.read_text())
print(json.dumps({"name": data.get("name")} | {k:data.get(k) for k in ["scripts","imports","type","tsup"]}, indent=2, sort_keys=True))
else:
print("no package.json")
PY
echo
echo "Top-level tsconfig relevant fields:"
python3 - <<'PY'
import json
from pathlib import Path
p=Path('tsconfig.json')
if p.exists():
data=json.loads(p.read_text())
print(json.dumps({k:data.get(k) for k in ["extends","compilerOptions","include","exclude","references"]}, indent=2, sort_keys=True))
else:
print("no top-level tsconfig.json")
PY
echo
echo "Top-level bunfig relevant:"
if [ -f bunfig.toml ]; then cat -n bunfig.toml; else echo "no bunfig.toml"; fi
echo
echo "Other root aliases/imports:"
rg -n -C2 '"imports"|"alias"|[Aa]lias|root:\s*[^#]+|baseUrl|resolve|alias:' --glob 'package.json' --glob 'tsconfig*.json' --glob 'vite.config.*' --glob 'bunfig.toml' .Repository: claude-code-best/claude-code
Length of output: 7679
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json, re
p='package.json'
data=json.load(open(p))
root=re.compile(r'^\s*root\s*=?\s*["\']?([^"'\n\#]+)["\']?\s*$|^\s*root\s*=\s*["\']?([^"'\n\#]+)["\']?\s*$')
tsconfig=json.load(open('tsconfig.json'))
files=[]
for f in ['src/utils/tasks.js', 'src/services/api/taskAnchorReminder.js']:
try:
from pathlib import Path
files.append((f, Path(f).exists()))
except Exception as e:
files.append((f, f'<error: {e}>'))
print(json.dumps({
"root_root_alias": re.search(root, open('bunfig.toml').read()).group(1) if root.search(open('bunfig.toml').read()) else None,
"package_root_alias": data.get("root"),
"tsconfig_paths_src": tsconfig.get("compilerOptions", {}).get("paths", {}).get("src/*"),
"files_exist": dict(files)
}, indent=2))
PYRepository: claude-code-best/claude-code
Length of output: 424
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "bunfig root settings:"
python3 - <<'PY'
from pathlib import Path
p=Path('bunfig.toml')
text=p.read_text()
for line in text.splitlines():
s=line.strip()
if s.startswith('root'):
print(line)
print(bool(p.exists()))
PY
echo
echo "package root settings:"
python3 - <<'PY'
import json
data=json.load(open('package.json'))
print(data.get('root'))
PY
echo
echo "tsconfig src alias:"
python3 - <<'PY'
import json
data=json.load(open('tsconfig.json'))
print(data.get('compilerOptions',{}).get('paths',{}).get('src/*'))
PY
echo
echo "target files exist:"
python3 - <<'PY'
from pathlib import Path
for f in ['src/utils/tasks.js', 'src/services/api/taskAnchorReminder.js']:
print(f"{f}: {Path(f).exists()}")
PYRepository: claude-code-best/claude-code
Length of output: 359
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Search exact specifier targets:"
rg -n "'src/utils/tasks\.js'|'src/services/api/taskAnchorReminder\.js'|utils/tasks|taskAnchorReminder" --glob '!node_modules' .
echo
echo "Top-level manifest root package files:"
fd -HI -t f '^root\.js$|^src$' . -d 4 | sed 's#^\./##'
echo
echo "REPL snippet context:"
sed -n '3525,3550p' src/screens/REPL.tsx 2>/dev/null || trueRepository: claude-code-best/claude-code
Length of output: 4906
Use relative dynamic imports for the notice injection.
src/* is only configured in TypeScript and Vite config; Bun package imports are not configured with a src/ alias, so these bare imports can resolve differently at native Bun runtime. Use ../utils/tasks.js and ../services/api/taskAnchorReminder.js instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/screens/REPL.tsx` around lines 3533 - 3536, Update the dynamic imports in
the notice injection within REPL to use the relative paths ../utils/tasks.js and
../services/api/taskAnchorReminder.js instead of src/ aliases, while preserving
the existing imported symbols and Promise.all flow.
| export function isAnchorlessNudge(text: string): boolean { | ||
| const trimmed = text.trim() | ||
| if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) { | ||
| return false | ||
| } | ||
| return ANCHORLESS_NUDGE_PATTERN.test(trimmed) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reject nudges that include a task anchor.
Line 25 treats any short text containing a nudge word as anchorless. For example, continue Task #2`` matches and injects a reminder telling the model to continue the highest-priority task, overriding the user’s explicit task selection. Exclude explicit task references (and add regression coverage for them).
Proposed direction
+const TASK_REFERENCE_PATTERN = /(?:task|任务)\s*#?\d+\b/i
+
export function isAnchorlessNudge(text: string): boolean {
const trimmed = text.trim()
if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) {
return false
}
- return ANCHORLESS_NUDGE_PATTERN.test(trimmed)
+ return (
+ ANCHORLESS_NUDGE_PATTERN.test(trimmed) &&
+ !TASK_REFERENCE_PATTERN.test(trimmed)
+ )
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function isAnchorlessNudge(text: string): boolean { | |
| const trimmed = text.trim() | |
| if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) { | |
| return false | |
| } | |
| return ANCHORLESS_NUDGE_PATTERN.test(trimmed) | |
| const TASK_REFERENCE_PATTERN = /(?:task|任务)\s*#?\d+\b/i | |
| export function isAnchorlessNudge(text: string): boolean { | |
| const trimmed = text.trim() | |
| if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) { | |
| return false | |
| } | |
| return ( | |
| ANCHORLESS_NUDGE_PATTERN.test(trimmed) && | |
| !TASK_REFERENCE_PATTERN.test(trimmed) | |
| ) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/services/api/taskAnchorReminder.ts` around lines 20 - 25, Update
isAnchorlessNudge to reject trimmed text containing an explicit task anchor
before applying ANCHORLESS_NUDGE_PATTERN, while preserving existing length and
nudge-word validation. Reuse the existing task-reference detection symbol if
available, and add regression coverage confirming inputs such as “continue Task
`#2`” return false.
现象:
长开发任务跑到一半反复"停住"(像卡顿),由于这个任务并非我完全关心的,在每次卡住后我按顺序发了 6 条催促:
后期模型"忘记任务要做什么"、编辑把文件写出"重复的、不可能编译的行",陷入自我核实打转。执行
/rewind退回后恢复正常。这个现象我先前在qwen模型上遇到过几次,属于是模型注意力被稀释后导致的任务锚点漂移轨迹问题,目前看起来opus 4.8对于长任务 + 连续无锚点注入也会出现这个问题。由于无法保障用户都知道存在模型注意力被稀释后导致的任务锚点漂移轨迹问题,同时用户并行使用agent,并不一定能够对当前任务足够重视,从而在长任务卡住时也可能出现连续输出“继续 ”的情况,期望在harness层进行一定的保障。下面我采用了token 友好的方式,对该问题进行修复
修复方案:
长会话反复无锚点催促("继续/接着做")会导致任务锚点衰减、模型过早
end_turn 打转。新增两层客户端护栏(均 token 友好):
Summary by CodeRabbit
New Features
Bug Fixes